/*
 * // Bristleback plugin - Copyright (c) 2010 bristleback.googlecode.com
 * // ---------------------------------------------------------------------------
 * // This program is free software; you can redistribute it and/or modify it
 * // under the terms of the GNU Lesser General Public License as published by the
 * // Free Software Foundation; either version 3 of the License, or (at your
 * // option) any later version.
 * // This library is distributed in the hope that it will be useful,
 * // but without any warranty; without even the implied warranty of merchantability
 * // or fitness for a particular purpose.
 * // You should have received a copy of the GNU Lesser General Public License along
 * // with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
 * // ---------------------------------------------------------------------------
 */

package pl.bristleback.server.bristle.binding;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import pl.bristleback.server.bristle.binding.resolvers.PropertyValueResolver;
import pl.bristleback.server.bristle.exceptions.BristleRuntimeException;
import pl.bristleback.server.bristle.utils.Getter;
import pl.bristleback.server.bristle.utils.Setter;
import pl.bristleback.server.bristle.utils.StringUtil;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Property information wrapper, containing information about name, getter/setter methods, children.
 * It also contains validation data.
 * <p/>
 * Created on: 2011-03-03 12:18:20 <br/>
 *
 * @author Wojciech Niemiec
 */
public class NestedFieldInformation {
  private static Logger log = Logger.getLogger(NestedFieldInformation.class.getName());

  private NestedFieldInformation root;
  private boolean intermediary;
  private boolean required;

  private String name;
  private String fullPathName;
  private boolean containerInPath;
  private Class<?> fieldClass;
  private NestedFieldInformation elementFieldInformation;
  private Getter getter;
  private Setter setter;
  private Map<String, NestedFieldInformation> children = new HashMap<String, NestedFieldInformation>();

  private PropertyValueResolver resolver;

  /**
   * Creates a root field information element with filled basic information.
   *
   * @param rootClass root field class.
   * @param rootName  root field name.
   * @return root field information.
   */
  public static NestedFieldInformation createRoot(Class rootClass, String rootName) {
    NestedFieldInformation rootInformation = new NestedFieldInformation();
    rootInformation.setName(rootName);
    rootInformation.setFullPathName(StringUtils.EMPTY);
    rootInformation.setFieldClass(rootClass);
    rootInformation.setRoot(rootInformation);
    rootInformation.setIntermediary(true);
    return rootInformation;
  }

  /**
   * Creates and returns a child of this field information. It adds newly created child to the children list.
   * Sets name, full path name and root.
   *
   * @param childName child name.
   * @return newly created child instance.
   */
  public NestedFieldInformation addChild(String childName) {
    if (getChild(childName) != null) {
      throw new BristleRuntimeException("Cannot add a child with name " + childName + ".");
    }
    NestedFieldInformation child = new NestedFieldInformation();
    child.setRoot(root);
    child.setName(childName);
    setFullPath(childName, child);
    children.put(childName, child);
    return child;
  }

  private void setFullPath(String childName, NestedFieldInformation child) {
    if (StringUtils.isEmpty(fullPathName)) {
      child.setFullPathName(childName);
    } else {
      child.setFullPathName(fullPathName + StringUtil.DOT + childName);
    }
  }

  public boolean isContainer() {
    return isList() || isArray() || isMap();
  }

  public boolean isList() {
    return List.class.isAssignableFrom(getFieldClass());
  }

  public boolean isArray() {
    return getFieldClass().isArray();
  }

  public boolean isMap() {
    return Map.class.isAssignableFrom(getFieldClass());
  }

  public NestedFieldInformation getChild(String childName) {
    return children.get(childName);
  }

  public NestedFieldInformation getRoot() {
    return root;
  }

  public void setRoot(NestedFieldInformation root) {
    this.root = root;
  }

  public Getter getGetter() {
    return getter;
  }

  public void setGetter(Getter getter) {
    this.getter = getter;
  }

  public Setter getSetter() {
    return setter;
  }

  public void setSetter(Setter setter) {
    this.setter = setter;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public boolean isIntermediary() {
    return intermediary;
  }

  public void setIntermediary(boolean intermediary) {
    this.intermediary = intermediary;
  }

  public Class<?> getFieldClass() {
    return fieldClass;
  }

  public void setFieldClass(Class<?> fieldClass) {
    this.fieldClass = fieldClass;
  }

  public Map<String, NestedFieldInformation> getChildren() {
    return children;
  }

  public Collection<NestedFieldInformation> getChildrenList() {
    return children.values();
  }

  public PropertyValueResolver getResolver() {
    return resolver;
  }

  public void setResolver(PropertyValueResolver resolver) {
    this.resolver = resolver;
  }

  public boolean isRequired() {
    return required;
  }

  public void setRequired(boolean required) {
    this.required = required;
  }

  public NestedFieldInformation getElementFieldInformation() {
    return elementFieldInformation;
  }

  public void setElementFieldInformation(NestedFieldInformation elementFieldInformation) {
    this.elementFieldInformation = elementFieldInformation;
  }

  public String getFullPathName() {
    return fullPathName;
  }

  public void setFullPathName(String fullPathName) {
    this.fullPathName = fullPathName;
  }

  public boolean isContainerInPath() {
    return containerInPath;
  }

  public void setContainerInPath(boolean containerInPath) {
    this.containerInPath = containerInPath;
  }
}
